﻿
//using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Anno.EngineData.Cache
{
    /// <summary>
    /// 内存缓存
    /// </summary>
    public class MemoryCache : ICache
    {
        private readonly System.Runtime.Caching.MemoryCache _memoryCache;

        public MemoryCache(string name = "")
        {
            if (name.IsNullOrEmpty())
                name = this.GetType().Name;
            _memoryCache = new System.Runtime.Caching.MemoryCache(name);
        }

        public long Del(params string[] key)
        {
            foreach (var k in key)
            {
                _memoryCache.Remove(k);
            }

            return key.Length;
        }

        public Task<long> DelAsync(params string[] key)
        {
            foreach (var k in key)
            {
                _memoryCache.Remove(k);
            }
#if NET40
            return TaskEx.FromResult((long)key.Length);
#else
            return Task.FromResult((long)key.Length);
#endif
        }

//        public async Task<long> DelByPatternAsync(string pattern)
//        {
//            if (string.IsNullOrEmpty(pattern))
//                return 0;

//            //pattern = Regex.Replace(pattern, @"\{*.\}", "(.*)");
//            var keys = GetAllKeys().Where(k => k.StartsWith(pattern));
//            if (keys != null && keys.Any())
//#if NET40
//                return DelAsync(keys.ToArray()).Result;
//#else
//                return await DelAsync(keys.ToArray());
//#endif

//            return 0;
//        }

        public bool Exists(string key)
        {
            return _memoryCache.Contains(key);
        }

        public Task<bool> ExistsAsync(string key)
        {
#if NET40
            return TaskEx.FromResult(Exists(key));
#else
            return Task.FromResult(Exists(key));
#endif
        }

        public string Get(string key)
        {
            return _memoryCache.Get(key)?.ToString();
        }

        public T Get<T>(string key)
        {
            var cacheItem = _memoryCache.GetCacheItem(key);
            if (!cacheItem.IsNullOrEmpty() && cacheItem.Value is T value)
            {
                return value;
            }
            return default(T);
        }

        public Task<string> GetAsync(string key)
        {
#if NET40
            return TaskEx.FromResult(Get(key));
#else
            return Task.FromResult(Get(key));
#endif
        }

        public Task<T> GetAsync<T>(string key)
        {
#if NET40
            return TaskEx.FromResult(Get<T>(key));
#else
            return Task.FromResult(Get<T>(key));
#endif
        }

        public bool Set(string key, object value)
        {
            var cacheItemPolicy = CreatePolicy(null, null);
            _memoryCache.Set(key, value, cacheItemPolicy);
            return true;
        }

        public bool Set(string key, object value, TimeSpan expire)
        {
            var cacheItemPolicy = CreatePolicy(expire, true);
            _memoryCache.Set(key, value, cacheItemPolicy);
            return true;
        }

        public Task<bool> SetAsync(string key, object value)
        {
#if NET40
            return TaskEx.FromResult(Set(key, value));
#else
            return Task.FromResult(Set(key, value));
#endif
        }

        public Task<bool> SetAsync(string key, object value, TimeSpan expire)
        {
#if NET40
            return TaskEx.FromResult(Set(key, value, expire));
#else
            return Task.FromResult(Set(key, value, expire));
#endif
        }

        public List<string> GetAllKeys()
        {
            const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
            var entries = _memoryCache.GetType().GetField("_entries", flags).GetValue(_memoryCache);
            var cacheItems =
                entries.GetType().GetProperty("Keys").GetValue(entries) as ICollection<object>; //entries as IDictionary;
            var keys = new List<string>();
            if (cacheItems == null) return keys;
            return cacheItems.Select(u => u.ToString()).ToList();
            //foreach (DictionaryEntry cacheItem in cacheItems)
            //{
            //    keys.Add(cacheItem.Key.ToString());
            //}
            //return keys;
        }
        public bool Set(string key, object value, int expirationMinutes = 0)
        {
            if (value.IsNullOrEmpty())
                throw new ArgumentNullException(nameof(value));

            Set(key, value, TimeSpan.FromMinutes(expirationMinutes));

            return true;
        }
        public Task<bool> SetAsync(string key, object value, int expirationMinutes = 0)
        {
#if NET40
            return TaskEx.FromResult(Set(key, value, expirationMinutes));
#else
            return Task.FromResult(Set(key, value, expirationMinutes));
#endif
        }
        /// <summary>
        /// 新增或更新缓存
        /// </summary>
        /// <param name="key">缓存项的唯一标识符</param>
        /// <param name="value">缓存项的数据</param>
        /// <param name="expiresTime">缓存时长(分钟), enum ExpiresTime</param>
        /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
        /// <returns></returns>
        public bool Set(string key, object value, ExpiresTime expiresTime = ExpiresTime.None)
        {
            int expirationMinutes = expiresTime.GetHashCode();
            return Set(key, value, TimeSpan.FromMinutes(expirationMinutes));
        }
        public T Get<T>(string key, Func<T> cachePopulate, ExpiresTime expiresTime = ExpiresTime.Hours_1, Func<T, bool> checkPopulate = null)
        {
            var cacheItem = Get<T>(key);
            if (!cacheItem.IsNullOrEmpty())
            {
                return cacheItem;
            }
            else if (cachePopulate != null)//调用fucloadData获取数据
            {
                var val = cachePopulate();
                if (!val.IsNullOrEmpty())//缓存
                {
                    if (checkPopulate == null || checkPopulate?.Invoke(val) == true)
                        Set(key, val, expiresTime == ExpiresTime.None ? ExpiresTime.Hours_1 : expiresTime);
                    return val;
                }
            }
            return default(T);
        }
        public Task<T> GetAsync<T>(string key, Func<T> cachePopulate, ExpiresTime expiresTime = ExpiresTime.Hours_1, Func<T, bool> checkPopulate = null)
        {
#if NET40
            return TaskEx.FromResult(Get<T>(key, cachePopulate, expiresTime, checkPopulate));
#else
            return Task.FromResult(Get<T>(key, cachePopulate, expiresTime, checkPopulate));
#endif
        }

        public T Get<T>(string key, Func<T> cachePopulate, TimeSpan? expiresTime, Func<T, bool> checkPopulate = null)
        {
            var cacheItem = Get<T>(key);
            if (!cacheItem.IsNullOrEmpty())
            {
                return cacheItem;
            }
            else if (cachePopulate != null)//调用fucloadData获取数据
            {
                var val = cachePopulate();
                if (!val.IsNullOrEmpty())//缓存
                {
                    if (checkPopulate == null || checkPopulate?.Invoke(val) == true)
                        Set(key, val, expiresTime ?? TimeSpan.FromMinutes(60));
                    return val;
                }
            }
            return default(T);
        }
        public Task<T> GetAsync<T>(string key, Func<T> cachePopulate, TimeSpan? expiresTime, Func<T, bool> checkPopulate = null)
        {
#if NET40
            return TaskEx.FromResult(Get<T>(key, cachePopulate, expiresTime, checkPopulate));
#else
            return Task.FromResult(Get<T>(key, cachePopulate, expiresTime, checkPopulate));
#endif
        }

        #region CreatePolicy
        private void checkKey(string key)
        {
            if (key.IsNullOrEmpty())
                throw new ArgumentNullException(nameof(key));
        }
        /// <summary>
        /// 创建一个表示一组特定的缓存项的逐出和有效期的详细信息
        /// </summary>
        /// <param name="slidingExpiration">指示是否应在指定的持续时间之后逐出缓存项</param>
        /// <param name="absoluteExpiration">指示是否应在给定时段内未访问，就会对它进行是否逐出缓存项</param>
        /// <returns></returns>
        private static System.Runtime.Caching.CacheItemPolicy CreatePolicy(TimeSpan? slidingExpiration, DateTimeOffset? absoluteExpiration)
        {
            var policy = new System.Runtime.Caching.CacheItemPolicy();

            if (absoluteExpiration.HasValue)
            {
                policy.AbsoluteExpiration = absoluteExpiration.Value;
            }
            else if (slidingExpiration.HasValue)
            {
                policy.SlidingExpiration = slidingExpiration.Value;
            }
            policy.Priority = System.Runtime.Caching.CacheItemPriority.Default;

            return policy;
        }

        /// <summary>
        /// 创建一个表示一组特定的缓存项的逐出和有效期的详细信息
        /// </summary>
        /// <param name="expiresIn">缓存时长</param>
        /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
        /// <returns></returns>
        private System.Runtime.Caching.CacheItemPolicy CreatePolicy(TimeSpan? expiresIn, bool isSliding = true)
        {
            if (isSliding)
            {
                return CreatePolicy(expiresIn, null);
            }
            else
            {
                DateTimeOffset? absoluteExpiration = null;
                if (expiresIn.HasValue)
                    absoluteExpiration = DateTimeOffset.Now.Add(expiresIn.Value);
                return CreatePolicy(null, absoluteExpiration);
            }
        }
        /// <summary>
        /// 创建一个表示一组特定的缓存项的逐出和有效期的详细信息
        /// </summary>
        /// <param name="expirationMinutes">缓存时长(分钟)</param>
        /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
        /// <returns></returns>
        private System.Runtime.Caching.CacheItemPolicy CreatePolicy(int? expirationMinutes = 30, bool isSliding = true)
        {
            TimeSpan? expiresIn = null;
            if (expirationMinutes.HasValue)
                expiresIn = DateTimeOffset.Now.AddMinutes(expirationMinutes.Value) - DateTimeOffset.Now;
            return CreatePolicy(expiresIn, isSliding);
        }
#endregion
    }
}